/**
 * @file ustring.h
 * @brief Работа со строками и символами Юникода
 * @version 1.0
 * @date 12 августа 2011
 * @author Владимир Керимов
 */

#pragma once

#include <qua/encode.h>
#include <string>
#include <iostream>
#include <stdexcept>

/// Пространство имён программного инструментария Qualab
namespace qua
{
    /// Класс символа Unicode
    class uchar
    {
    public:
        /// @brief Задать ASCII символ
        /// @details Задаётся с проверкой вхождения аргумента в ASCII
        void set_ascii( char sign )
        {
            if( encode::is_not_ascii( sign ) )
                throw std::out_of_range( "Code is out of ASCII!" );
            uch.code &= encode::MAX_ASCII;
            uch.ascii = sign;
        }

        /// Задать символ ассоциированный с кодовой страницей
        void set_char( char sign, encode::codepage cp = encode::cpDEFAULT )
        {
            uch.code = encode::to_unicode( sign, cp );
        }

        /// Задать символ UCS-2 / UTF-16
        void set_ucs2( wchar_t sign )
        {
            uch.code &= encode::MAX_UCS2;
            uch.ucs2 = sign;
        }

        /// @brief Задать код символа Unicode
        /// @details Задаётся с проверкой вхождения аргумента в Unicode
        void set_code( int code )
        {
            if( encode::is_not_unicode( code ) )
                throw std::out_of_range( "Code is out of Unicode!" );
            uch.code = code;
        }

        /// @brief Взять символ ASCII
        /// @details Берётся с проверкой вхождения кода символа в ASCII
        char get_ascii() const
        {
            if( is_not_ascii() )
                throw std::out_of_range( "Code is out of ASCII!" );
            return uch.ascii;
        }

        /// Взять символ ассоциированный с кодовой страницей
        char get_char( encode::codepage cp = encode::cpDEFAULT ) const
        {
            return encode::to_char( uch.code, cp );
        }

        /// @brief Взять символ UCS-2 / UTF-16
        /// @details Берётся с проверкой вхождения кода символа в UCS-2
        wchar_t get_ucs2() const
        {
            if( is_not_ucs2() )
                throw std::out_of_range( "Code is out of UCS-2!" );
            return uch.ucs2;
        }

        /// @brief Взять код символа Unicode
        /// @details Берётся с проверкой вхождения кода символа в Unicode
        int get_code() const
        {
            if( is_not_unicode() )
                throw std::out_of_range( "Code is out of UTF-8!" );
            return uch.code;
        }

        /// @brief Размер кода символа в байтах
        /// @details Размер: 1 - для ASCII; 2 - для UCS-2; 4 - для остального Unicode
        size_t size() const
        {
            return encode::is_ascii( uch.code ) ? 1 : ( encode::is_ucs2( uch.code ) ? 2 : 4 ) ;
        }

        /// Взять код символа без проверки
        int code() const { return uch.code; }

        /// Взять без проверки символ UCS-2 / UTF-16
        wchar_t ucs2() const { return uch.ucs2; }

        /// Взять без проверки символ ASCII
        char ascii() const { return uch.ascii; }

        /// @brief Приведение к типу char
        /// @details Согласно странице кодировки заданной по умолчанию
        operator char () const { return get_char(); }

        /// @brief Приведение к типу wchar_t
        /// @details С проверкой на вхождение кода символа в UCS-2
        operator wchar_t () const { return get_ucs2(); }

        /// @brief Приведение к типу int
        /// @details С проверкой на вхождение кода символа в Unicode
        operator int () const { return get_code(); }

        /// Проверка: код символа входит в ASCII
        bool is_ascii() const { return encode::is_ascii( uch.code ); }

        /// Проверка: код символа входит в UCS-2
        bool is_ucs2() const { return encode::is_ucs2( uch.code ); }

        /// Проверка: код символа входит в Unicode
        bool is_unicode() const { return encode::is_unicode( uch.code ); }

        /// Проверка: код символа не входит в ASCII
        bool is_not_ascii() const { return encode::is_not_ascii( uch.code ); }

        /// Проверка: код символа не входит в UCS-2
        bool is_not_ucs2() const { return encode::is_not_ucs2( uch.code ); }

        /// Проверка: код символа не входит в Unicode
        bool is_not_unicode() const { return encode::is_not_unicode( uch.code ); }

    private:
        // @brief Объединение возможных интерпретаций Unicode
        // @details char для ASCII; wchar_t для UCS-2; int для Unicode
        union uch_t
        {
            char    ascii;  ///< используется для символов ASCII
            wchar_t ucs2;   ///< используется для символов UCS-2 / UTF-16
            int     code;   ///< используется для всех символов Unicode
        } uch; ///< рабочее поле объекта, интерпретируется согласно значению
    };

    /// Нулевой символ Unicode
    extern const uchar uchar_null;

    /// Преобразование символа из кодовой страницы в символ Unicode
    inline uchar to_uchar( char sign, encode::codepage cp = encode::cpDEFAULT )
    {
        uchar uch;
        uch.set_char( sign, cp );
        return uch;
    }

    /// Преобразование символа UCS-2 в символ Unicode
    inline uchar to_uchar( wchar_t sign )
    {
        uchar uch;
        uch.set_ucs2( sign );
        return uch;
    }

    /// @brief Преобразование создание символа Unicode по заданному коду
    /// @details Преобразование с проверкой вхождения кода символа в Unicode
    inline uchar to_uchar( int code )
    {
        uchar uch;
        uch.set_code( code );
        return uch;
    }

    /// Класс работы со строками Unicode как со стандартным строковым контейнером
    typedef std::basic_string< uchar > ustring;

    /// @brief Вывод символа Unicode в стандартный поток вывода std::ostream
    /// @details Используется кодовая страница заданная по умолчанию
    inline std::ostream& operator << ( std::ostream& os, uchar sign )
    {
        return os << sign.get_char();
    }

    /// @brief Ввод символа Unicode из стандартого потока ввода std::istream
    /// @details Используется кодовая страница заданная по умолчанию
    inline std::istream& operator >> ( std::istream& is, uchar& sign )
    {
        char ch;
        is >> ch;
        sign.set_char( ch );
        return is;
    }

    /// @brief Вывод символа Unicode в стандартный поток вывода std::wostream
    /// @details С проверкой вхождения кода символа в UCS-2
    inline std::wostream& operator << ( std::wostream& os, uchar sign )
    {
        return os << sign.get_ucs2();
    }

    /// @brief Ввод символа Unicode из стандартого потока ввода std::wistream
    /// @details С проверкой вхождения кода символа в UCS-2
    inline std::wistream& operator >> ( std::wistream& is, uchar& sign )
    {
        wchar_t wc;
        is >> wc;
        sign.set_ucs2( wc );
        return is;
    }

    /// @brief Преобразование в строку Unicode цепочки символов по заданной кодовой странице
    /// @details Строку можно ограничить по длине, по умолчанию используется длина вычисляемая std::strlen()
    ustring to_ustring( const char* text, encode::codepage cp = encode::cpDEFAULT, size_t len = 0 );

    /// @brief Преобразование в строку Unicode цепочки символов UCS-2
    /// @details Строку можно ограничить по длине, по умолчанию используется длина вычисляемая std::wcslen()
    ustring to_ustring( const wchar_t* text, size_t len = 0 );

    /// Преобразование в строку Unicode стандартной строки std::string по заданной кодовой странице
    inline ustring to_ustring( const std::string& text, encode::codepage cp = encode::cpDEFAULT )
    {
        return to_ustring( text.c_str(), cp, text.size() );
    }

    /// Преобразование в строку Unicode стандартной строки std::wstring
    inline ustring to_ustring( const std::wstring& text )
    {
        return to_ustring( text.c_str(), text.size() );
    }

    /// Преобразование строки Unicode в стандартную строку std::string по указанной кодовой странице
    std::string to_string( const ustring& text, encode::codepage cp = encode::cpDEFAULT );

    /// @brief Преобразование строки Unicode в стандартную строку std::wstring
    /// @details Преобразование с проверкой вхождения каждого символа строки в UCS-2
    std::wstring to_wstring( const ustring& text );

    /// @brief Преобразование строки Unicode в стандартную строку std::string закодированную в формате UTF-8
    /// @details Строка в формате UTF-8 может получится длиннее засчёт символов не входящих в ASCII
    std::string to_utf8( const ustring& text );

    /// @brief Вывод строки Unicode в стандартный поток вывода std::ostream
    /// @details Используется кодовая страница заданная по умолчанию
    inline std::ostream& operator << ( std::ostream& os, const ustring& str )
    {
        return os << ( (encode::get_default_codepage() == encode::UTF8) ? to_utf8( str ) : to_string( str ) );
    }

    /// @brief Ввод строки Unicode из стандартого потока ввода std::istream
    /// @details Используется кодовая страница заданная по умолчанию
    inline std::istream& operator >> ( std::istream& is, ustring& str )
    {
        std::string inp;
        is >> inp;
        str = to_ustring( inp );
        return is;
    }

    /// @brief Вывод строки Unicode в стандартный поток вывода std::wostream
    /// @details Вывод с проверкой вхождения каждого символа строки в UCS-2
    inline std::wostream& operator << ( std::wostream& os, const ustring& str )
    {
        return os << to_wstring( str );
    }

    /// @brief Ввод строки Unicode из стандартого потока ввода std::wistream
    /// @details Ввод с проверкой вхождения каждого символа строки в UCS-2
    inline std::wistream& operator >> ( std::wistream& is, ustring& str )
    {
        std::wstring inp;
        is >> inp;
        str = to_ustring( inp );
        return is;
    }
}
